// 大文件上传

package api 

import (
	"fmt"
	"os"
	"crypto/md5"
	"encoding/hex"
	"net/http"
	"io/ioutil"
	"encoding/json"
	"strings"
	"strconv"
	"mime/multipart"
	"bytes"
	"sync"
	"io"
	"path"

)

func init() {
	content, err := ioutil.ReadFile("./token.txt")
	if err != nil {
		fmt.Println("token 配置文件读取错误， 请检查")
		return 
	}
	access_token = string(content)
	print(access_token)
}

var access_token string = ""

type PreUploadObj struct {
	Path string `json:"path"`
	UploadId string `json:"uploadid"`
	BlockList []int `json:"block_list"`
	FileName string `json:"filename"`
}

type ReturnMD5 struct {
	MD5 string `json:"md5"`
}

type FileInfoUpload struct {
	Path string `json:"path"`
	IsDir int `json:"isdir"`
	FsId int64 `json:"fs_id"`
	Errno int `json:"errno"`
	MD5 string `json:"md5"`
}

func repeatUpload(rowMD5 string) {
	//重新传输错误的分片

}

func ReadBigFile(fileName string) ([]string, []string, int64, string, error) {
    f, err := os.Open(fileName)
    if err != nil {
        fmt.Println("can't opened this file", err.Error())
        return nil, nil, 0, "null", err
    }
	defer f.Close()
	trueName := path.Base(fileName)
	file, _:= os.Stat(fileName)
	size := file.Size() //获取文件大小
	fmt.Println("文件大小为：" + strconv.FormatInt(size, 10))
	s := make([]byte, 4 * (1 << 20))
	
	hashList := []string{}
	// fileList := [][]byte{}
	tempFileNameList := []string{}

	index := 0
    for {
        nr, err := f.Read(s[:])
        if err != nil && err != io.EOF {
			fmt.Println("read file err ", err.Error())
			os.Exit(0)
		}
		if nr == 0 {
			// EOF
			// fmt.Println("hash list is :" , hashList)
            break
		}
			
		//计算分片的md5
		sliceHash := md5.New()
		// sliceHash.Write(s[0:nr])
		io.Copy(sliceHash, bytes.NewReader(s[0:nr]))
		MD5Str := hex.EncodeToString(sliceHash.Sum(nil))
		hashList = append(hashList, MD5Str)
		// fileList = append(fileList, s[0:nr])

		////尝试下保存为临时文件的方法。。
		tempFileName := "./" + trueName + strconv.Itoa(index) + ".temp"
		tempFileNameList = append(tempFileNameList, tempFileName)
		tempFile, err := os.Create(tempFileName)
		defer tempFile.Close()
		if err != nil {
			fmt.Println("临时文件创建失败" + err.Error())
			os.Exit(0)
		}
		io.Copy(tempFile, bytes.NewReader(s[0:nr]))
		index += 1
	
	}

	return hashList, tempFileNameList, size, trueName, nil 
	
    // return hashList, fileList, size, trueName, nil 
}

func PreUpload(filename string, filesize int64, blockList []string) PreUploadObj {
	//预上传
	postParams := `path=/upload_data/` + filename + `&size=` + strconv.FormatInt(filesize, 10) + `&isdir=0&autoinit=1&rtype=3&block_list=`
	fmt.Println("正在准备上传.......")
	fmt.Println("共需要上传:" + strconv.Itoa(len(blockList)) + "个分片")
	for i, blockMD5 := range(blockList) {
		if i == 0 {
			postParams += `["` + blockMD5 + `"`
		} else {
			postParams += `,"` + blockMD5 + `"`
		}
	}
	postParams += "]"
	resp, err := http.Post("http://pan.baidu.com/rest/2.0/xpan/file?method=precreate&access_token=" + access_token,
        "application/x-www-form-urlencoded",
        strings.NewReader(postParams))
    if err != nil {
		fmt.Println(err.Error())
		return PreUploadObj{}
    }

    defer resp.Body.Close()
    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
		// handle error
		fmt.Println(err.Error())
		return PreUploadObj{} 
    }

	// fmt.Println(string(body))
	preUploadReturn := PreUploadObj{}
	err = json.Unmarshal(body, &preUploadReturn) 
	if err != nil {
		fmt.Println(err.Error())
		return PreUploadObj{} 
	}
	// fmt.Println(preUploadReturn)
	preUploadReturn.FileName = filename //设置上文件名字
	return preUploadReturn
}

func SliceUpload(preUploadObj PreUploadObj, tempFileNameList []string, hashList []string) []string {
	blockList := preUploadObj.BlockList//获取分片编号
	if len(blockList) == 0 {
		//说明是一个分片
		blockList = append(blockList, 0)
	}
	uploadId := preUploadObj.UploadId
	path := "/upload_data/" + preUploadObj.FileName

	var safeMap sync.Map
	var errMap sync.Map//保存错误的分片

	fmt.Println("开始上传...")
	gpool := New(3)
	// fmt.Println(blockList)
	for i, _ := range(blockList) {
		// fmt.Println(i)
		gpool.Add(1)
		go func(index int) {
			blockId := blockList[index]
			// buf := new(bytes.Buffer)
			// w := multipart.NewWriter(buf)
			// fileContent := fileList[index]
			tempFileName := tempFileNameList[index]
			tempFile, err := os.Open(tempFileName)
			defer tempFile.Close()
			if err != nil {
				fmt.Println("打开文件分片失败" + err.Error())
				os.Exit(0)
			}
			buf := new(bytes.Buffer)
			bodyWriter := multipart.NewWriter(buf)
			formDataContentType := bodyWriter.FormDataContentType()
			fw, _ := bodyWriter.CreateFormFile("file", tempFileName)

			// io.Copy(fw, bytes.NewReader(fileContent))
			io.Copy(fw, tempFile)
		
			bodyWriter.Close()
			
			postUrl := `https://d.pcs.baidu.com/rest/2.0/pcs/superfile2?` + `&method=upload&type=tmpfile&path=` + path + `&uploadid=` + uploadId + `&partseq=` + strconv.Itoa(blockId) + `&app_id=250528&BDUSS=JwdkM2MGo1NUNmb1BoOVBhZkR-cGNkbnZNVXc2QzdGd1VaVFJrZWMwb290VXhmRUFBQUFBJCQAAAAAAAAAAAEAAAADU5O6eDkyMDIzMjc5NgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACgoJV8oKCVfTV`
			resp, err := http.Post(postUrl,
				formDataContentType,
				buf)
				
			if err != nil {
				fmt.Println("post请求错误")
				fmt.Println(err.Error())
				
				// errMap.Store(blockId, fileContent)//存储错误的分片id和内容
				errMap.Store(blockId, tempFileName)// 存储临时文件名字
				gpool.Done()
				return 
    		}

    		defer resp.Body.Close()
			body, err := ioutil.ReadAll(resp.Body)
			fmt.Println(string(body))	
			if err != nil {
				// handle error
				fmt.Println("返回内容读取错误")
				fmt.Println(err.Error())
				// errMap.Store(blockId, fileContent)//存储错误的分片id和内容
				errMap.Store(blockId, tempFileName)// 存储临时文件名字
				gpool.Done()
				return 
			}
			// fmt.Println("hh")
			// fmt.Println(string(body) + strconv.Itoa(index))
			returnMD5 := ReturnMD5{}
			err = json.Unmarshal(body, &returnMD5)
			if err != nil {
				fmt.Println("json 解析错误", string(body))
				fmt.Println(err.Error())
				// errMap.Store(blockId, fileContent)//存储错误的分片id和内容
				errMap.Store(blockId, tempFileName)// 存储临时文件名字
				gpool.Done()
				return 
			}
			//检查返回的md5对不对
			// fmt.Println(string(body))
			rowMD5 := hashList[index]
			// fmt.Println(rowMD5 + "," + returnMD5.MD5)
			if rowMD5 == returnMD5.MD5 {
				//
				safeMap.Store(index, returnMD5.MD5) // 存储md5串
				fmt.Println("分片:" + strconv.Itoa(index) + "上传完毕")
				gpool.Done()
			} else {
				//说明分片不正确 重新传输
				// repeatUpload(rowMD5)
				fmt.Println(rowMD5)
				fmt.Println("~~~~~")
				fmt.Println(returnMD5.MD5)
				fmt.Println("分片:" + strconv.Itoa(index) + "传输不正确")
				errMap.Store(blockId, tempFileName)// 存储临时文件名字
				gpool.Done()
			}
			
		}(i)
	}
	gpool.Wait()
	fmt.Println("正在检查上传失败的分片，请稍等...")

	//在这重新上传下错误的分片
	errMap.Range(func(k, v interface{}) bool {
		// fmt.Println("iterate:", k, v)
		blockId := k.(int)
		tempFileName := v.(string)
		fileContent, err := os.Create(tempFileName)
		defer fileContent.Close()
		if err != nil {
			fmt.Println("临时文件打开失败，请重试")
			os.Exit(0)
		}
		buf := new(bytes.Buffer)
		bodyWriter := multipart.NewWriter(buf)
		formDataContentType := bodyWriter.FormDataContentType()
		fw, _ := bodyWriter.CreateFormFile("file", "tempfile")
		io.Copy(fw, fileContent)
		bodyWriter.Close()
		postUrl := `https://d.pcs.baidu.com/rest/2.0/pcs/superfile2?` + `&method=upload&type=tmpfile&path=` + path + `&uploadid=` + uploadId + `&partseq=` + strconv.Itoa(blockId) + `&app_id=250528&BDUSS=JwdkM2MGo1NUNmb1BoOVBhZkR-cGNkbnZNVXc2QzdGd1VaVFJrZWMwb290VXhmRUFBQUFBJCQAAAAAAAAAAAEAAAADU5O6eDkyMDIzMjc5NgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACgoJV8oKCVfTV`
		resp, err := http.Post(postUrl,
			formDataContentType,
			buf)
			
		if err != nil {
			fmt.Println("post请求错误")
			fmt.Println(err.Error())
			return true
		}

		defer resp.Body.Close()
		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			// handle error
			fmt.Println("返回内容读取错误")
			fmt.Println(err.Error())
			
			return true
		}
		
		returnMD5 := ReturnMD5{}
		err = json.Unmarshal(body, &returnMD5)
		if err != nil {
			fmt.Println("json 解析错误")
			fmt.Println(err.Error())
			return true
		}
		safeMap.Store(blockId, returnMD5.MD5) // 存储md5串
		fmt.Println("分片:" + strconv.Itoa(blockId) + "重新上传完毕")

        return true
	})

	i := 0
	returnMD5List := []string{}
	safeMap.Range(func(k, v interface{}) bool {
		// fmt.Println("iterate:", k, v)
		value, _ := safeMap.Load(i)
		returnMD5List = append(returnMD5List, value.(string))
		i += 1
        return true
	})
	// fmt.Print("返回的md5 list is ")
	// fmt.Println(returnMD5List)
	//删除临时文件
	for _, fileName := range(tempFileNameList) {
		os.Remove(fileName)
		
	}
	return returnMD5List //返回下一步创建文件需要的md5串
}

func CreateFile(returnMD5List []string, filesize int64, uploadid string, filename string ) {
	postParams := `path=/upload_data/` + filename + `&size=` + strconv.FormatInt(filesize, 10) + `&isdir=0&uploadid=` + uploadid + `&rtype=3&block_list=`
	
	for i, blockMD5 := range(returnMD5List) {
		if i == 0 {
			postParams += `["` + blockMD5 + `"`
		} else {
			postParams += `,"` + blockMD5 + `"`
		}
	}
	postParams += "]"
	resp, err := http.Post("https://pan.baidu.com/rest/2.0/xpan/file?method=create&access_token=" + access_token,
        "application/x-www-form-urlencoded",
        strings.NewReader(postParams))
    if err != nil {
		fmt.Println(err.Error())
		return 
    }

    defer resp.Body.Close()
    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
		// handle error
		fmt.Println(err.Error())
		return 
    }

	// fmt.Println(string(body))
	fileInfo := FileInfoUpload{}
	err = json.Unmarshal(body, &fileInfo)
	if err != nil {
		fmt.Println(err.Error())
		return
	}

	if fileInfo.Errno == 0 {
		fmt.Println("上传成功!")
		fmt.Println("上传路径为: " + fileInfo.Path + ", 上传文件id为: " + fileInfo.Path + "文件md5为: " + fileInfo.MD5)
		
	} else {
		fmt.Println("上传出现问题,请重试")
		fmt.Println(string(body))
	}
}


// func main() {

// 	hashList, fileList, size, name, err := ReadBigFile("./明末清初.csv")
// 	if err != nil {
// 		return 
// 	}
// 	returns := preUpload(name, size, hashList)
	
// 	returnMD5List := sliceUpload(returns, fileList)

// 	CreateFile(returnMD5List, size, returns.UploadId, returns.FileName)
// }